הפקולטה למדעי המחשב פרופ' גיל ברקת נועם שגב, שרי דואק, רן ברואר, דור כהן הטכניון מכון טכנולוגי לישראל 70.70.07.4 מבוא לתכנות מערכות 024.00 מבחן מועד א' סמסטר חורף 07.207.4 הוראות כלליות משך המבחן: 081 דקות )שלוש שעות(. טופס המבחן מכיל XX עמודים, כולל עמוד זה. יש לענות על כל השאלות במקום המיועד לכך בטופס. מותר לכתוב גם בעט וגם בעיפרון. מומלץ ביותר ראשית לקרוא כל שאלה עד סופה, ורק אח"כ לענות. מותר השימוש בכל חומר עזר כתוב או מודפס. יש להקפיד על כתיבה ברורה ומסודרת של התשובות. על התשובות להיות קצרות ומדויקות ככל הניתן. אין לרשום קוד מיותר או עובדות כלליות שאינן קשורות לפתרון. אין צורך לתעד את הקוד בפתרונות. אין צורך להקפיד על code conventions כלשהם בעת כתיבת קוד. מבנה המבחן: שאלה. 0 2 4 5 6 סה"כ ניקוד /07 /6 /9 /07 /27 /.5 /.77 בהצלחה! 1
שאלה :. ADT 07( C נק'( בשאלה זו עליך לממש ADT בשם TRN )קיצור של )tournament התומך בטורניר בענף ספורט לבחירת המשתמש הלקוח מן הקבוצה כדורגל, כדורסל, כדוריד, בייסבול{. בניגוד למקובל, ניתן להגדיר עבור כל טורניר בנפרד האם מותרת תוצאת תיקו, ומה הניקוד הניתן עבור ניצחון/תיקו/הפסד. טורניר נערך בין n קבוצות, כאשר כל קבוצה מארחת בדיוק פעם אחת את כל יריבותיה. )לכן, כל קבוצה משחקת בדיוק 22n משחקים.( לקבוצות אין שמות, אלא פשוט אינדקסים בתחום 1n...0. הזוכה בטורניר היא הקבוצה אשר זכתה במירב הנקודות, ואם יש צורך בשובר שויון, הקבוצה אשר הפרש שעריה/סליה/וכד' טוב יותר, ואם עדיין יש צורך בשובר שויון, זו אשר ניצחה פעמים רבות יותר. סעיף א' )7. נק'( רשום קובץ מנשק התומך בפעולות הבאות: יצירת טורניר של n קבוצות בענף ספורט כלשהן. א. הוספת תוצאה של משחק לטורניר. ב. החזרת הקבוצה אשר זכתה בטורניר. ג. הריסת טורניר. ד. #ifndef _TRN #define _TRN typedef enum {FOOTBALL, BASEKETBALL, HANDBALL, BASEBALL Field; typedef enum {RESULT_ALREADY_EXISTS, TOURNAMENT_DID_NOT_END_YET, NO_SINGLE_WINNER,... TRN_Result; typedef struct TRN_rec* TRN; TRN TRN_Create (unsigned int n_teams, // how many teams Field field, // redundant, never used later Boolean draw_allowed, // whether or not draws are allowed Unsigned int points [3]); // points given for win/draw/loss TRN_Result TRN_AddGame (TRN trn, int first_team, int second_team, int result[2]); // Other combinations can work TRN_Result TRN_Winner (TRN trn, int* winner); void TRN_Destroy (TRN trn); סעיף ב' )5 נק'( עבור כל אחת מן הפעולות א', ב' וג' בסעיף הקודם, ציין/י קוד שגיאה ספציפי. :TRN_Create מספר קבוצות לא חוקי )0(, ניקוד לא הגיוני )למשל, ניקוד גבוה יותר לתיקו מאשר לניצחון( :TRN_AddGame משחק דווח כבר, אינדקס קבוצה לא חוקי :TRN_Winner הטורניר לא הסתיים )לא שוחקו עדיין כל המשחקים(, אין מנצח יחיד )כל שוברי השוויון לא עזרו( סעיף ג' )5 נק'( על איזה מבנה נתונים )אחד או יותר( אשר נילמד בכיתה היית מבסס/ת את?TRN מותר לבצע שינויים סבירים במבנה בו בחרת, אך יש לציין במפורש את כל השינויים. שני פתרונות פשוטים: א. גרף מכוון, בו כוון קשת מציין מי הקבוצה המארחת. בנוסף, יש להוסיף לקשת שדה תוצאה. ב. מטריצה רב מימדית [n][n][2][2] בה שני האינדקסים הראשונים הם הקבוצות, האינדקס השלישי הוא זהות המארחת, והאינדקס הרביעי עבור התוצאה. 2
)6 נק'(: שאלה C 0 A.h B.h main.c #ifndef A_H_ #define A_H_ #include <stdio.h> #ifndef B_H_ #define B_H_ #ifdef UNIX void print_unix(const char** in) { printf ("UNIX %s",*in); #else void print_windows(const char* in) { printf ("WINDOWS %s",in); #include <stdbool.h> #define UNIX true #include "A.h" נתונים הקבצים הבאים: #include "C.h" int main (int argc, char** argv){ print("hello"); return 0; קבע/י עבור כל אחת משלוש האפשרויות של,C.h האם התוכנית main.c תתקמפל ותרוץ תהיה השגיאה, ואם כן, מה יהיה הפלט של הרצתה. כהלכה. מה אם לא, 1) C.h 2) C.h 3) C.h #ifndef C_H_ #define C_H_ #include "B.h" #ifndef C_H_ #define C_H_ #include "B.h" #include "A.h" #ifndef C_H_ #define C_H_ #include "A.h" #include "B.h" #ifdef UNIX {print_unix(&in); #else {print_windows(in); #ifdef UNIX {print_unix(&in); #else {print_windows(in); #ifdef UNIX {print_unix(&in); #else {print_windows(in); לא מוגדר בעת שימוש print_unix 1 UNIX hello 2 UNIX hello 3 שגיאת קומפילציה 3
)9 נק'(: שאלה C 2 typedef struct node *Node; struct node { int n; Node next, prev; ; נתון מימוש פשוט של צומת עבור רשימה מקושרת דוכיוונית של מספרים שלמים :)int( תזכורת: רשימה מעגלית היא רשימה בה האיברים הראשון והאחרון מחוברים. כתב/י את הפונקציה doublel המקבלת את הצומת הראשון של רשימה מקושרת מעגלית. הפונקציה מחזירה רשימה מקושרת מעגלית חדשה אשר מכילה כל איבר מהרשימה המקורית פעמיים. לדוגמא, אם הרשימה המקורית כללה את הערכים 2 ו 5 )בסדר הזה(, הרשימה החדשה תכיל את הערכים 5 2, 2, ו 5 )בסדר הזה(. במקרה של כישלון, יש להחזיר.NULL שימו לב שהרשימות הן ללא איבר דמה. הפונקציה destroylist מקבלת את תחילתה של רשימה מקושרת דוכיוונית לא מעגלית חוקית ומשחררת את המידע השמור ברשימה ואת הזיכרון של הרשימה. ניתן להניח כי הפונקציה destroylist ממומשת וחתימתה נתונה: void destroylist(node); Node doublel(node head) { if (!head) return NULL; DLNode newhead = createdlnodes(head); if (!newhead) return NULL; DLNode tail = newhead>next; for (DLNode itr = head>next; itr!=head; iter=iter>next;){ if(!(tail>next = createdlnodes(itr))) { destroydllist(newhead); return NULL; tail>next>prev = tail; tail = tail>next>next; return newhead; static DLNode createdlnodes(dlnode in){ DLNode tmp1 = createdlnode(in); DLNode tmp2 = createdlnode(in); if (tmp1 && tmp2){ tmp1>next = tmp2; tmp2>prev = tmp1; return tmp1; else { free(tmp1); free(tmp2); return NULL; static DLNode createdlnode(dlnode in){ DLNode tmp = malloc(sizeof(*tmp)); if(tmp){ tmp>next = tmp>preav = NULL; tmp>n = in>n; return tmp; 4
)00 נק'(: Bash שאלה 4 בחברת הריגול האינטרנטית scroogel הגיעו למסקנה כי בידיהם משאב לא מנוצל כהלכה, העובדים בחברה. כדי לנצלם כהלכה, הוחלט על ידי הנהלת החברה להשתיל מערכות ריגול זעירות בעורף של כל עובד. כל מערכת ריגול שכזאת שולחת כל שניה מידע לשרתים הראשיים. המידע הנשלח בנוי ממחרוזת מזהה של העובד, שעה, מיקום, תיאור תמציתי )שתי מילים( מה העובד עושה. לדוגמא, שתי השורות הבאות יכולות להישלח למערכת : סעיף א' )0 נק'( 75A2FCE9 05:24:09 48.8566:2.3522 sleep dreaming 75A2FCE9 10:54:45 45.5822:3.3267 work presentation בשלב ראשון, המערכת הושתלה בעובדים בסניף הקטן של החברה בישראל. מטרת החברה היא לתת חיזוק חיובי לאלו המשקיעים יותר שעות בעבודה מאחרים וחיזוק שלילי לאלו המבזבזים זמן. כתוב/כתבי שורה אחת באמצעותה ניתן למצוא את העובדים המשקיעים את רוב זמנם בעבודה. על הפלט להיות מספרי הזיהוי של חמישים העובדים המשקיעים יותר זמן בעבודה משאר העובדים, ממוין בסדר יורד לפי שעות העבודה. שם הקובץ הוא.spy.israel כל מחרוזת בקובץ מופרדת באמצעות רווחים. ניתן לזהות עובדים אשר מבצעים עבודה לפי המילה הראשונה בתיאור התמציתי, שתהיה.work ניתן להניח כי הקובץ מכיל מספר שווה של שורות לכל עובד, כלומר, כל עובד נבדק על אותה תקופת זמן. ניתן להניח כי המחרוזת המזהה מכילה רק מספרים ואותיות ראשיות letters).(uppercase ניתן להניח כי מחרוזות התיאור מכילות רק אותיות לא ראשיות letters(.)lowercase אם שני עובדים עבדו אותו משך זמן, אין משמעות לסדר ההדפסה. כלומר, עליך להדפיס את מחרוזות הזיהוי של 51 העובדים להם יש מספר השורות הרב ביותר המכילה את המילה "work" בשדה המתאים. פתרון לדוגמא א' cat spy.israel cut d" " f1,4 grep w 'work' sort uniq c sort rbn cut d" " f2 head n50 פתרון לדוגמא ב' grep ' work ' cut d" " f1 sort uniq c sort rnb head n50 cut c9 סעיף ב' )4 נק'( איך הייתה משתנה התשובה לסעיף הקודם אם הדרישה הייתה כי על הפלט להיות מספרי הזיהוי של חמישים העובדים המשקיעים את רוב זמנם לא בעבודה, וממוין בסדר עולה? היינו מוסיפים דגל "v" לgrep ואת המיון השני היינו מבצעים ללא הדגל "r". סעיף ג' ).. נק'( המנהלים היו מרוצים מהתוצאות הראשוניות של הניסוי. זיהוי עובדים מצטיינים ותגמולם ופיטורי העובדים הלא מוצלחים העלה את פוריות הסניף. כדי להתקדם לשלב השני של הניסוי, יש לטפל במספר מסקנות אשר עלו מהשלב הראשון. המילה הראשונה בתיאור התמציתי לא מספיקה. הניסוי הבא יתבצע על מספר סניפים במספר מדינות. עובדים אשר עובדים יותר שעות אינם בהכרח היעילים או הטובים ביותר. כעת עליך לכתוב תסריט אשר ידפיס את חמישים העובדים המוצלחים בחברה. התסריט מקבל שני פרמטרים, הראשון הינו קובץ הגדרות המכיל מספר שורות, כל שורה מכילה מספר ומחרוזת של שתי מילים. הפרמטר השני הוא קובץ, כמו קובץ spy.israel שראינו קודם, או ספריה. על התסריט למיין את רשימת העובדים בקובץ spy כפי שיוסבר בהמשך, או אם התקבלה ספריה במקום קובץ יש לבצע את הפעולה רקורסיבית על כל קבצי הspy והספריות תחת הספרייה הנתונה. דוגמא לקובץ הגדרות חוקי: 2 travel work 5
1 work present בחירת חמישים העובדים תתבצע על פי הקריטריונים הבאים: א. לשם קביעת הצלחה, נתייחס רק לשורות המכילות תיאור תמציתי אשר מופיע בקובץ ההגדרות. המספר לצד התיאור התמציתי הינו ציון העבודה )לא העובד(. ב. עובדים אשר ביצעו עבודה אשר מתוארת בקובץ ההגדרות עם ציון i באופן אוטומטי יחשבו לטובים יותר מעובדים אשר ביצעו עבודה מתוארת בקובץ ההגדרות עם ציון 1+i. ג. עובד יכול להופיע בפלט מספר פעמים אך לכל היותר פעם אחת לכל פעולה )תיאור תמציתי(. ד. מבין שני עובדים אשר ביצעו עבודה המדורגת בציון i יועדף זה אשר ביצע אותה יותר פעמים. ניתן להניח כי אף עובד לא מופיע בשני קבצי.spy שים/י לב, אין להשתמש בקבצים זמניים. function printall { if [[ f $1 ]]; then cat $1 else for f in `ls`; do printall "$1/$f" done fi פתרון לדוגמא א' function findtext { while read line; do printall $2 grep " ${line$" cut d" " f1 sort uniq c sort rnb cut c9 done sort n $1 cut d" " f2 findtext head n50 פתרון לדוגמא ב' )בלי פונקציות עזר( priorities=() sort n $1 while read a line; do priorities[${line[1]]="${line[*]:1" done for p in priorities; do grep r " $p$" $2 cut d":" f2 cut d" " f1" sort uniq c sort nr cut c9 done head n50 6
)27 נק'( :C++ שאלה 5 בשאלה זו אנו נרצה ליצור את התשתית לפעולות מתמטיות מורכבות. אנו נרצה לממש סט מחלקות המאפשרות הפעלת פונקציות על ערך כלשהו, כמו גם שרשור פונקציות מתמטיות. סעיף א' )6 נק'( כתוב/כתבי את המחלקה האבסטרקטית.Function המחלקה מתארת פעולה אחת, כלומר פונקציה מתמטית יחידה. שים/י לב, סוגים שונים של Function יוכלו לעבוד על טיפוסים מתמטיים פרימיטיביים כמו גם על טיפוסים מתמטיים מורכבים, אך Function יחיד יכול לעבוד על טיפוס אחד בלבד. ניתן להניח כי פונקציה מתמטית עובדת על ערך יחיד ומחזירה ערך מאותו הטיפוס. template <class T> class Function { virtual ~Function(){ virtual T operator() (const T&) const = 0; Adder וChainer כך שהקוד הבא יתקמפל וירוץ כנדרש: int main() { Adder<int> adder(5); // adder(x) = x + 5 adder.set(3) // adder(x) = x + 3 const Multiplyer<int> multiplyer(4); // multiplyer(x) = x * 4 cout << adder(2) << endl; // adder(2) = 5 סעיף ב' )5. נק'( כתוב/כתבי את המחלקות Chainer<int> chainer(adder); chainer.add(multiplyer); // chainer(x) = (x + 3) * 4 cout << chainer(2) << endl; // chainer(2) = 20 Chainer<int> chainer2; // chainer2(x) = x const Multiplyer<int> multiplyer2(3); // multiplyer2(x) = x * 3 const Adder<int> adder2(2); // adder2(x) = x + 2 chainer2.add(multiplyer2); chainer2.add(adder2); // chainer2(x) = (x * 3) + 2 chainer.add(chainer2); // chainer(x) = chainer2((x + 3) * 4) cout << chainer(2) << endl; // chainer(2) = chainer2(20) = 62 return 0; ניתן להשתמש באוספים מה STL לצורך מימוש סעיף זה. ניתן להניח כי המחלקה Multiplier ממומשת. אין לכתוב קוד מיותר או לא נדרש. template<class T> class Adder : Function<T>{ T val; explicit Adder(const T& n) : val(n){ set(const T& n) {val = n T operator() (const T& in) { return in+val; template<class T> class Chainer : Function<T>{ 7
std::vector<const Function<T>* > functions; Chainer(const Function<T>& func) : functions(1,&func) { Chainer() =default; add(const Function<T>& func) {functions.pushback(&func); T operator() (const T& in) { T val = in; for (auto& it : functions){ const Function<T>& func = *it; val = func(val); return val; copy c'tor operator+ operator<< opeartor= d'tor סעיף ג' )4 נק'( מהן הדרישות על הטיפוס הטמפלייטי T בהגדרת המחלקות?Adder, FunctionChainer סעיף ד' )5 נק'( מספר מהנדסים ניסו להשתמש בתשתית שכתבנו אך שמו לב להתנהגות משונה. למשל, התוכנית הבא מדפיסה 8. int main() { Adder<int> adder(5); // adder(x) = x + 5 Chainer<int> chainer(adder); // chainer (x) = x + 5 adder.set(3); // adder(x) = x + 3 chainer.add(adder); // chainer (x) = (x + 5) + 3 cout << chainer(2) << endl; // chainer(2) = 10 return 0; הסביר/י מדוע הדבר קורה ומצא/י עוד שגיאה אשר עלולה להתרחש בזמן ריצה. הדבר קורה מאחר שchainer מחזיק מצביע לפונקציה ולא עותק של פונקציה. הדבר גם יגרום לשגיאה בזמן ריצה אם הפונקציה תחזיר Chainer ותנסה להשתמש בו, מאחר שכל המצביעים יהיו למשתנים לוקאליים שכבר נהרסו בזמן סגירת הפונקציה 8
:C++.5( נק'( שאלה 6 ממוצע פונקציונאלי הינו ממוצע חשבוני של תוצאות הפעלת פונקציה על ערכים שונים, לא בהכרח מספריים, בתנאי שתוצאת הפונקציה תמיד מספרית. ברצוננו לחשב ממוצע פונקציונאלי של איברים במבנה נתונים כלשהו. סעיף א' )8 נק'( כתוב/כתבי את הפונקציה funcavg אשר מקבלת תחום כלשהו ומחשבת ממוצע פונקציונאלי באותו תחום עבור פונקציה לבחירת המשתמש. אם אין איברים בתחום יש לזרוק חריגה מתאימה. template <class Iterator, class Operator> double funcavg(iterator begin, Iterator end, Operator op){ if (begin == end) throw EmptyRangeException int num = 0; double sum = 0; for (Iterator it = begin; it!= end; ++it){ ++num; sum += op(*it); return (sum/num); סעיף ב' )0 נק'( בתרגולים ראינו את המחלקה Shape ומספר מחלקות היורשות ממנה. כתוב/כתבי את הפונקציה largeaverage אשר מקבלת אוסף סדור )ווקטור( של צורות ומחזירה את ממוצע השטח של שלושת הצורות בעלות השטח הגדול ביותר. כמו כן, יש לכתוב כל קוד נוסף שנדרש כדי שהפונקציה תעבור הידור וקישור ותרוץ. ניתן להניח כי הווקטור ממוין על פי שטח בסדר עולה. ניתן להניח כי בווקטור ישנם לפחות שלושה איברים. יש להשתמש בפונקציה funcavg מהסעיף הקודם. תזכורת: Shape.h Square.h Circle.h class Shape { int center_x, center_y; Shape(int x, int y) : center_x(x), center_y(y) { virtual ~Shape() { virtual double area() const = 0; ; class Circle : public Shape { int radius; Circle(int x, int y, int radius) : Shape(x,y), radius(radius) { virtual double area() const { return radius*radius*pi; ; class Square : public Shape { int edge; Square(int x, int y, int edge) : Shape(x,y), edge(edge) { virtual double area() const { return edge*edge; ; double largeaverage(std::vector<shape*>& in){ return funcavg(in.end()3, in.end(), Area()); class Area { double operator() (Shape* shape) { return shape>area(); 9